/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package model;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import view.MonteCarloWindow;

/**
 *
 * @author aaaz
 */
public final class MonteCarloFunction implements ViewReqInterface {

   

    @Override
    public void StopThread(int nBar) {       
        bosThreads.get(nBar).interrupt();        
    }
    private List<Thread> bosThreads = new ArrayList<Thread>();
    private int nBar = 0;

    @Override
    public void MonteCarloStandardOption(String callPutFlag, double s, double x, double t, double r, double b, double v, int nSteps, int nSimulations, MonteCarloWindow monteCarloWindow, int nThreads) {

        bosThreads.add(new Thread(new bosThread(callPutFlag, s, x, t, r, b, v, nSteps, nSimulations, monteCarloWindow, nBar, nThreads)));
        bosThreads.get(nBar).start();
         //UNCOMMENT FOR DEBUG TIMES 
        /*
        try {
            bosThreads.get(nBar).join();
        } catch (InterruptedException ex) {
            Logger.getLogger(MonteCarloFunction.class.getName()).log(Level.SEVERE, null, ex);
        }
        */  
        nBar++;
    }

    private class bosThread implements Runnable {

        private String callPutFlag;
        private double s, x, t, r, b, v;
        private int nSteps, nSimulations;
        private MonteCarloWindow monteCarloWindow;
        private final int currentBar, nThreads;

        private List<Thread> workerThreads;

        private int currentThread;

        private List<Integer> threadSims = new ArrayList<>();
        private List<Double> threadValue = new ArrayList<>();
        


        public bosThread(String callPutFlag, double s, double x, double t, double r, double b, double v, int nSteps, int nSimulations, MonteCarloWindow monteCarloWindow, int currentBar, int nThreads) {
            
            this.callPutFlag = callPutFlag;
            this.s = s;
            this.t = t;
            this.r = r;
            this.b = b;
            this.v = v;
            this.x = x;
            this.nSteps = nSteps;
            this.nSimulations = nSimulations;
            this.monteCarloWindow = monteCarloWindow;
            this.currentBar = currentBar;
            this.nThreads = nThreads;
            this.workerThreads = new ArrayList<>();
            this.currentThread = 0;
            
        }

        @Override
        public void run() {
            long startTime = System.currentTimeMillis();
            monteCarloWindow.addBar(this.currentBar);
            monteCarloWindow.setInfoText(this.currentBar, "F: " + this.callPutFlag + " S: " + this.s + " X: " + this.x + " T: " + this.t + " R: " + this.r + " B: " + this.b + " V: " + this.v + " Steps: " + this.nSteps + " Simulations: " + this.nSimulations);

            int iterationThread = nSimulations / nThreads;
            if (iterationThread == 0) {
                threadSims.add(0);
                this.workerThreads.add(new Thread(new monteThread(this.callPutFlag, this.s, this.x, this.t, this.r, this.b, this.v, this.nSteps, nSimulations, this.currentBar, 0)));
            } else {
                for (int t = 0; t < nThreads; t++) {
                    threadSims.add(0);
                    threadValue.add(0.0);
                    if (t == nThreads - 1) {
                        this.workerThreads.add(new Thread(new monteThread(this.callPutFlag, this.s, this.x, this.t, this.r, this.b, this.v, this.nSteps, iterationThread + (nSimulations % nThreads), this.currentBar, t)));
                    } else {
                        this.workerThreads.add(new Thread(new monteThread(this.callPutFlag, this.s, this.x, this.t, this.r, this.b, this.v, this.nSteps, iterationThread, this.currentBar, t)));
                    }

                    this.workerThreads.get(this.currentThread).start();
                    this.currentThread++;

                }
            }
            for (Thread thread : workerThreads) {
                try {
                    thread.join();
                } catch (InterruptedException ex) {                    
                    for (Thread thread2 : workerThreads) {
                        thread2.interrupt();
                    }
                    monteCarloWindow.setBarStatus(currentBar, 2);
                    return;

                }
            }

            int done = 0;
            for (Iterator<Integer> it = threadSims.iterator(); it.hasNext();) {
                done += it.next();
            }
            double sum = 0.0;
            for (Iterator<Double> it = threadValue.iterator(); it.hasNext();) {
                sum += it.next();
            }
            double finValue = Math.exp(-r * t) * (sum / done);
            monteCarloWindow.setReslutValue(currentBar, finValue);
            monteCarloWindow.setPBarValue(currentBar, 100);
            monteCarloWindow.setBarStatus(currentBar, 1);
            long endTime = System.currentTimeMillis();
            System.out.println(nThreads+"\t"+nSimulations+"\t"+(endTime-startTime));

        }

        private void setIntermediatesValues(int currentThread, long runningMillisecods, long predictTime, int minutesR, int secondsR, int minutesL, int secondsL) {

            int done = 0;
            for (Iterator<Integer> it = threadSims.iterator(); it.hasNext();) {
                done += it.next();
            }
            double sum = 0.0;
            for (Iterator<Double> it = threadValue.iterator(); it.hasNext();) {
                sum += it.next();
            }

            double intValue = Math.exp(-r * t) * (sum / done);
            this.monteCarloWindow.setReslutValue(currentBar, intValue);
            double percentaje = (done * 100.0 / this.nSimulations);
            this.monteCarloWindow.setPBarValue(currentBar, (int) (Math.ceil(percentaje)));
            this.monteCarloWindow.setPBarText(currentBar, String.format("%03.3f", percentaje) + " % Running: " + TimeUnit.MILLISECONDS.toHours(runningMillisecods) + ":" + minutesR + ":" + secondsR + " Left: " + TimeUnit.MILLISECONDS.toHours(predictTime) + ":" + minutesL + ":" + secondsL);

        }

        private class monteThread implements Runnable {

            private String callPutFlag;
            private double s, x, t, r, b, v;
            private int nSteps, nSimulations;
            private MonteCarloWindow monteCarloWindow;
            private final int currentBar;
            private final int threadNumber;

            public monteThread(String callPutFlag, double s, double x, double t, double r, double b, double v, int nSteps, int nSimulations, int currentBar, int threadNumber) {
                this.callPutFlag = callPutFlag;
                this.s = s;
                this.t = t;
                this.r = r;
                this.b = b;
                this.v = v;
                this.x = x;
                this.nSteps = nSteps;
                this.nSimulations = nSimulations;
                this.currentBar = currentBar;
                this.threadNumber = threadNumber;
            }

            @Override
            public void run() {

                long startTime = System.currentTimeMillis();
                long runningMillisecods = 0;
                long predictTime = 0;
                long currentTime = 0;
                int minutesR = 0, secondsR = 0;
                int minutesL = 0, secondsL = 0;
                double percentaje;
                double returnValue = 0;
                double dt = 0;
                double st = 0;
                double sum = 0;
                double drift = 0;
                double vSqrdt = 0;
                int z = 0;
                dt = this.t / this.nSteps;
                drift = ((this.b - Math.pow(v, 2) / 2) * dt);
                vSqrdt = this.v * Math.sqrt(dt);
                if ("c".equals(this.callPutFlag)) {
                    z = 1;
                } else if ("p".equals(this.callPutFlag)) {
                    z = -1;
                }

                int nSimPrint;
                if (nSimulations < (100/nThreads)) {
                    nSimPrint = 1;
                } else {
                    nSimPrint = (nSimulations / (100/nThreads));
                }
                for (int i = 0; i <= this.nSimulations; i++) {
                    st = s;
                    for (int j = 0; j <= this.nSteps; j++) {
                        st = st * Math.exp(drift + vSqrdt * new Random().nextGaussian());
                    }
                    sum = sum + Math.max(z * (st - x), 0);
                    if (i % nSimPrint == 0.0 && i > 0) {

                        currentTime = System.currentTimeMillis();
                        runningMillisecods = currentTime - startTime;
                        predictTime = ((this.nSimulations - i) * runningMillisecods) / (i + 1);
                        minutesR = (int) TimeUnit.MILLISECONDS.toMinutes(runningMillisecods) % 60;
                        secondsR = (int) TimeUnit.MILLISECONDS.toSeconds(runningMillisecods) % 60;
                        minutesL = (int) TimeUnit.MILLISECONDS.toMinutes(predictTime) % 60;
                        secondsL = (int) TimeUnit.MILLISECONDS.toSeconds(predictTime) % 60;
                        threadSims.set(threadNumber, i);
                        threadValue.set(threadNumber, sum);
                        setIntermediatesValues(currentThread, runningMillisecods, predictTime, minutesR, secondsR, minutesL, secondsL);

                    }
                    if (Thread.interrupted()) {
                        return;
                    };

                }

                threadSims.set(threadNumber, nSimulations);
                threadValue.set(threadNumber, sum);
                returnValue = Math.exp(-r * t) * (sum / this.nSimulations);
                currentTime = System.currentTimeMillis();
                runningMillisecods = currentTime - startTime;
                setIntermediatesValues(currentThread, runningMillisecods, predictTime, minutesR, secondsR, minutesL, secondsL);

            }
        }
    }

}
